/*
 * The MIT License 
 *
 * Copyright (c) 2011 pf-miles
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package action.expr;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import action.expr.ast.Action;
import action.expr.ast.Addend;
import action.expr.ast.Assignator;
import action.expr.ast.Assignment;
import action.expr.ast.Assignments;
import action.expr.ast.CustomOp;
import action.expr.ast.Expr;
import action.expr.ast.Factor;
import action.expr.ast.Field;
import action.expr.ast.OpLevel1;
import action.expr.ast.OpLevel2;
import action.expr.ast.PrimaryLevel2Op;

/**
 * generates legal, random action expression code for tests
 * 
 * @author pf-miles 2011-6-21 下午09:49:43
 * 
 */
public class RandomActionCodeGenerator implements ActionVisitor {

    public void visitAction(Action action, Object... params) {
        Assignments a = new Assignments();
        action.setAssignments(a);
        a.accept(this, params);
    }

    public void visitAddend(Addend addend, Object... params) {
        int ifSingle = RandUtil.rand(2);
        switch (ifSingle) {
        case 1:
            // not single
            Factor l = new Factor();
            l.accept(this, params);
            OpLevel2 op = randOpLevel2();
            if (op instanceof CustomOp)
                ((CustomOp) op).accept(this, params);
            Factor r = new Factor();
            r.accept(this, params);
            addend.setLeft(l);
            addend.setOp(op);
            addend.setRight(r);
            break;
        case 2:
            // single
            Factor s = new Factor();
            s.accept(this, params);
            addend.setSingle(s);
            break;
        default:
            throw new RuntimeException("Impossible.");
        }
    }

    private OpLevel2 randOpLevel2() {
        int ifPrimary = RandUtil.rand(2);
        switch (ifPrimary) {
        case 1:
            // primary
            int index = RandUtil.rand(PrimaryLevel2Op.values().length) - 1;
            return PrimaryLevel2Op.values()[index];
        case 2:
            // custom
            CustomOp c = new CustomOp();
            c.setKey(RandUtil.randKey());
            return c;
        default:
            throw new RuntimeException("Impossible.");
        }
    }

    public void visitAssignment(Assignment assignment, Object... params) {
        Field f = new Field();
        f.accept(this, params);
        Assignator a = randAssignator();
        Expr e = new Expr();
        e.accept(this, params);
        assignment.setField(f);
        assignment.setAssignator(a);
        assignment.setExpr(e);
    }

    private Assignator randAssignator() {
        return Assignator.values()[RandUtil.rand(Assignator.values().length) - 1];
    }

    public void visitAssignments(Assignments assignments, Object... params) {
        int num = RandUtil.rand(50);
        List<Assignment> as = new ArrayList<Assignment>();
        for (int i = 0; i < num; i++) {
            Assignment a = new Assignment();
            a.accept(this, params);
            as.add(a);
        }
        assignments.setAs(as);
    }

    public void visitCustomOp(CustomOp customOp, Object... params) {
        customOp.setKey(RandUtil.randKey());
    }

    public void visitExpr(Expr expr, Object... params) {
        int ifSingle = RandUtil.rand(2);
        switch (ifSingle) {
        case 1:
            // not single
            Addend l = new Addend();
            l.accept(this, params);
            OpLevel1 op = randOpLevel1();
            Addend r = new Addend();
            r.accept(this, params);
            expr.setLeft(l);
            expr.setOp(op);
            expr.setRight(r);
            break;
        case 2:
            // single
            Addend s = new Addend();
            s.accept(this, params);
            expr.setSingle(s);
            break;
        default:
            throw new RuntimeException("Impossible.");
        }
    }

    private OpLevel1 randOpLevel1() {
        return OpLevel1.values()[RandUtil.rand(OpLevel1.values().length) - 1];
    }

    public void visitFactor(Factor factor, Object... params) {
        int r1 = RandUtil.rand(4);
        switch (r1) {
        case 1:
            // num
            factor.setNum(new BigDecimal(RandUtil.randNum()));
            break;
        case 2:
            // str
            factor.setStr(RandUtil.randStr());
            break;
        case 3:
            // field
            Field f = new Field();
            f.accept(this, params);
            factor.setField(f);
            break;
        case 4:
            // (expr)
            Expr e = new Expr();
            e.setQuoted(true);
            e.accept(this, params);
            factor.setExpr(e);
            break;
        default:
            throw new RuntimeException("Impossible.");
        }
    }

    public void visitField(Field field, Object... params) {
        field.setKey(RandUtil.randKey());
    }

}
